// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "media/cast/receiver/audio_decoder.h"

#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/sys_byteorder.h"
#include "media/cast/cast_defines.h"
#include "third_party/opus/src/include/opus.h"

namespace media {
namespace cast {

// Base class that handles the common problem of detecting dropped frames, and
// then invoking the Decode() method implemented by the subclasses to convert
// the encoded payload data into usable audio data.
class AudioDecoder::ImplBase
    : public base::RefCountedThreadSafe<AudioDecoder::ImplBase> {
 public:
  ImplBase(const scoped_refptr<CastEnvironment>& cast_environment,
           Codec codec,
           int num_channels,
           int sampling_rate)
      : cast_environment_(cast_environment),
        codec_(codec),
        num_channels_(num_channels),
        operational_status_(STATUS_UNINITIALIZED),
        seen_first_frame_(false) {
    if (num_channels_ <= 0 || sampling_rate <= 0 || sampling_rate % 100 != 0)
      operational_status_ = STATUS_INVALID_CONFIGURATION;
  }

  OperationalStatus InitializationResult() const {
    return operational_status_;
  }

  void DecodeFrame(scoped_ptr<EncodedFrame> encoded_frame,
                   const DecodeFrameCallback& callback) {
    DCHECK_EQ(operational_status_, STATUS_INITIALIZED);

    static_assert(sizeof(encoded_frame->frame_id) == sizeof(last_frame_id_),
                  "size of frame_id types do not match");
    bool is_continuous = true;
    if (seen_first_frame_) {
      const uint32 frames_ahead = encoded_frame->frame_id - last_frame_id_;
      if (frames_ahead > 1) {
        RecoverBecauseFramesWereDropped();
        is_continuous = false;
      }
    } else {
      seen_first_frame_ = true;
    }
    last_frame_id_ = encoded_frame->frame_id;

    scoped_ptr<AudioBus> decoded_audio = Decode(
        encoded_frame->mutable_bytes(),
        static_cast<int>(encoded_frame->data.size()));
    cast_environment_->PostTask(CastEnvironment::MAIN,
                                FROM_HERE,
                                base::Bind(callback,
                                           base::Passed(&decoded_audio),
                                           is_continuous));
  }

 protected:
  friend class base::RefCountedThreadSafe<ImplBase>;
  virtual ~ImplBase() {}

  virtual void RecoverBecauseFramesWereDropped() {}

  // Note: Implementation of Decode() is allowed to mutate |data|.
  virtual scoped_ptr<AudioBus> Decode(uint8* data, int len) = 0;

  const scoped_refptr<CastEnvironment> cast_environment_;
  const Codec codec_;
  const int num_channels_;

  // Subclass' ctor is expected to set this to STATUS_INITIALIZED.
  OperationalStatus operational_status_;

 private:
  bool seen_first_frame_;
  uint32 last_frame_id_;

  DISALLOW_COPY_AND_ASSIGN(ImplBase);
};

class AudioDecoder::OpusImpl : public AudioDecoder::ImplBase {
 public:
  OpusImpl(const scoped_refptr<CastEnvironment>& cast_environment,
           int num_channels,
           int sampling_rate)
      : ImplBase(cast_environment,
                 CODEC_AUDIO_OPUS,
                 num_channels,
                 sampling_rate),
        decoder_memory_(new uint8[opus_decoder_get_size(num_channels)]),
        opus_decoder_(reinterpret_cast<OpusDecoder*>(decoder_memory_.get())),
        max_samples_per_frame_(
            kOpusMaxFrameDurationMillis * sampling_rate / 1000),
        buffer_(new float[max_samples_per_frame_ * num_channels]) {
    if (ImplBase::operational_status_ != STATUS_UNINITIALIZED)
      return;
    if (opus_decoder_init(opus_decoder_, sampling_rate, num_channels) !=
            OPUS_OK) {
      ImplBase::operational_status_ = STATUS_INVALID_CONFIGURATION;
      return;
    }
    ImplBase::operational_status_ = STATUS_INITIALIZED;
  }

 private:
  ~OpusImpl() final {}

  void RecoverBecauseFramesWereDropped() final {
    // Passing NULL for the input data notifies the decoder of frame loss.
    const opus_int32 result =
        opus_decode_float(
            opus_decoder_, NULL, 0, buffer_.get(), max_samples_per_frame_, 0);
    DCHECK_GE(result, 0);
  }

  scoped_ptr<AudioBus> Decode(uint8* data, int len) final {
    scoped_ptr<AudioBus> audio_bus;
    const opus_int32 num_samples_decoded = opus_decode_float(
        opus_decoder_, data, len, buffer_.get(), max_samples_per_frame_, 0);
    if (num_samples_decoded <= 0)
      return audio_bus.Pass();  // Decode error.

    // Copy interleaved samples from |buffer_| into a new AudioBus (where
    // samples are stored in planar format, for each channel).
    audio_bus = AudioBus::Create(num_channels_, num_samples_decoded).Pass();
    // TODO(miu): This should be moved into AudioBus::FromInterleaved().
    for (int ch = 0; ch < num_channels_; ++ch) {
      const float* src = buffer_.get() + ch;
      const float* const src_end = src + num_samples_decoded * num_channels_;
      float* dest = audio_bus->channel(ch);
      for (; src < src_end; src += num_channels_, ++dest)
        *dest = *src;
    }
    return audio_bus.Pass();
  }

  const scoped_ptr<uint8[]> decoder_memory_;
  OpusDecoder* const opus_decoder_;
  const int max_samples_per_frame_;
  const scoped_ptr<float[]> buffer_;

  // According to documentation in third_party/opus/src/include/opus.h, we must
  // provide enough space in |buffer_| to contain 120ms of samples.  At 48 kHz,
  // then, that means 5760 samples times the number of channels.
  static const int kOpusMaxFrameDurationMillis = 120;

  DISALLOW_COPY_AND_ASSIGN(OpusImpl);
};

class AudioDecoder::Pcm16Impl : public AudioDecoder::ImplBase {
 public:
  Pcm16Impl(const scoped_refptr<CastEnvironment>& cast_environment,
            int num_channels,
            int sampling_rate)
      : ImplBase(cast_environment,
                 CODEC_AUDIO_PCM16,
                 num_channels,
                 sampling_rate) {
    if (ImplBase::operational_status_ != STATUS_UNINITIALIZED)
      return;
    ImplBase::operational_status_ = STATUS_INITIALIZED;
  }

 private:
  ~Pcm16Impl() final {}

  scoped_ptr<AudioBus> Decode(uint8* data, int len) final {
    scoped_ptr<AudioBus> audio_bus;
    const int num_samples = len / sizeof(int16) / num_channels_;
    if (num_samples <= 0)
      return audio_bus.Pass();

    int16* const pcm_data = reinterpret_cast<int16*>(data);
#if defined(ARCH_CPU_LITTLE_ENDIAN)
    // Convert endianness.
    const int num_elements = num_samples * num_channels_;
    for (int i = 0; i < num_elements; ++i)
      pcm_data[i] = static_cast<int16>(base::NetToHost16(pcm_data[i]));
#endif
    audio_bus = AudioBus::Create(num_channels_, num_samples).Pass();
    audio_bus->FromInterleaved(pcm_data, num_samples, sizeof(int16));
    return audio_bus.Pass();
  }

  DISALLOW_COPY_AND_ASSIGN(Pcm16Impl);
};

AudioDecoder::AudioDecoder(
    const scoped_refptr<CastEnvironment>& cast_environment,
    int channels,
    int sampling_rate,
    Codec codec)
    : cast_environment_(cast_environment) {
  switch (codec) {
    case CODEC_AUDIO_OPUS:
      impl_ = new OpusImpl(cast_environment, channels, sampling_rate);
      break;
    case CODEC_AUDIO_PCM16:
      impl_ = new Pcm16Impl(cast_environment, channels, sampling_rate);
      break;
    default:
      NOTREACHED() << "Unknown or unspecified codec.";
      break;
  }
}

AudioDecoder::~AudioDecoder() {}

OperationalStatus AudioDecoder::InitializationResult() const {
  if (impl_.get())
    return impl_->InitializationResult();
  return STATUS_UNSUPPORTED_CODEC;
}

void AudioDecoder::DecodeFrame(
    scoped_ptr<EncodedFrame> encoded_frame,
    const DecodeFrameCallback& callback) {
  DCHECK(encoded_frame.get());
  DCHECK(!callback.is_null());
  if (!impl_.get() || impl_->InitializationResult() != STATUS_INITIALIZED) {
    callback.Run(make_scoped_ptr<AudioBus>(NULL), false);
    return;
  }
  cast_environment_->PostTask(CastEnvironment::AUDIO,
                              FROM_HERE,
                              base::Bind(&AudioDecoder::ImplBase::DecodeFrame,
                                         impl_,
                                         base::Passed(&encoded_frame),
                                         callback));
}

}  // namespace cast
}  // namespace media
